#include "CFlow.h"

#include <string.h>
#include <QDebug>

#include "../COperationCodes.h"
#include "../CCoreException.h"
#include "../data/CFunction.h"
#include "../CVMDataTypes.h"
#include "../CGlobal.h"

void if_(CFrame& frame)
{
    if (frame.operandCount() < 2)
        throw CCoreException(CCoreException::VM_EXCEPTION_INSUFFICIENT_OPERANDS, "<operations.flow.if_>");

    char* trgt = frame.popOperand();

    if (!isTrue(frame.peekOperand()))
    {
        delete[] frame.popOperand();
        frame.pushOperand(trgt);
        goTo(frame);
    } else
    {
        delete[] trgt;
        delete[] frame.popOperand();
    }
}

void goTo(CFrame& frame)
{
    if (frame.operandCount() < 1)
        throw CCoreException(CCoreException::VM_EXCEPTION_INSUFFICIENT_OPERANDS, "<operations.flow.goTo>");

    int trgt = *(int*)frame.peekOperand();
    delete[] frame.popOperand();

    char* code = frame.code();
    int codeSize = frame.codeSize();

    if (trgt >= frame.codeSize())
        throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.goTo>");

    int pntr = 0;
    int i = 0;
    while(i++ < trgt)
    {
        switch((COperationCodes)code[pntr++])
        {
            case COperationCodes::PUSH:
            {
                if (codeSize - pntr < (int)sizeof(int))
                    throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.goTo>");

                int sz = *(int*)(frame.code() + pntr);

                if (codeSize - pntr < sz)
                    throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.goTo>");

                pntr += sizeof(int) + sz;
                break;
            }
            default: break;
        }
    }

    frame.pointer() = pntr;
}

void func(CFrame& frame)
{
    if (frame.operandCount() < 1)
        throw CCoreException(CCoreException::VM_EXCEPTION_INSUFFICIENT_OPERANDS, "<operations.flow.func>");

    int cnt = *(int*)frame.peekOperand();
    delete[] frame.popOperand();

    if (frame.operandCount() < cnt)
        throw CCoreException(CCoreException::VM_EXCEPTION_INSUFFICIENT_OPERANDS, "<operations.flow.func>");

    QVector<QString>* prmtrs = new QVector<QString>();
    for (int i = 0; i < cnt; i++)
    {
        char* oprnd = frame.popOperand();
        prmtrs->append(QString::fromUtf8(oprnd));
        delete[] oprnd;
    }

    char* code = frame.code();
    int codeSize = frame.codeSize();
    int& pntr = frame.pointer();
    int strPstn = pntr;
    int c = 1;
    while(c != 0)
    {
        if (codeSize - pntr < 1)
            throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.func>");

        switch((COperationCodes)code[pntr++])
        {
            case COperationCodes::PUSH:
            {
                if (codeSize - pntr < (int)sizeof(int))
                    throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.func>");

                int sz = *(int*)(frame.code() + pntr);

                if (codeSize - pntr < sz)
                    throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.func>");

                pntr += sizeof(int) + sz;
                break;
            }
            case COperationCodes::FUNC:
            {
                c++;
                break;
            }
            case COperationCodes::END:
            {
                c--;
                break;
            }
            default: break;
        }
    }

    char * fnctn = new char[sizeof(CFunction) + 1];
    fnctn[0] = (char)CVMDataTypes::FUNCTION;
    new((void*)(fnctn + 1)) CFunction(code + strPstn, pntr - strPstn, cnt, frame.variables(), prmtrs);
    frame.references().append(fnctn);

    frame.pushOperand(fnctn);
}

void call(QStack<CFrame*>& frames)
{
    CFrame& crntFrm = *frames.top();

    if (crntFrm.operandCount() < 1)
        throw CCoreException(CCoreException::VM_EXCEPTION_INSUFFICIENT_OPERANDS, "<operations.flow.call>");

    char* oprnd = crntFrm.popOperand();

    if (*oprnd != (char) CVMDataTypes::FUNCTION)
        throw CCoreException(CCoreException::VM_EXCEPTION_WRONG_OPERAND, "<operations.flow.call>");

    CFunction* fnctn = (CFunction*)(oprnd + 1);

    if (crntFrm.operandCount() < fnctn->paramCount())
        throw CCoreException(CCoreException::VM_EXCEPTION_INSUFFICIENT_OPERANDS, "<operations.flow.call>");

    for (int i = 0; i < fnctn->paramCount(); i++)
    {
        QString nm = fnctn->parameters()[i];
        char* prm = crntFrm.popOperand();
        fnctn->variables()[nm] = prm;
    }

    CFrame* frm = new CFrame(fnctn->code(), fnctn->codeSize(), fnctn->variables(), crntFrm.references());

    if (fnctn->isNative())
    {
        fnctn->action()(*frm, frames);
        delete frm;
    }
    else frames.append(frm);

    delete[] oprnd;
}

void ret(QStack<CFrame*>& frames)
{
    CFrame& crntFrm = *frames.top();

    if (crntFrm.operandCount() < 1)
        throw CCoreException(CCoreException::VM_EXCEPTION_UNEXPECTED_END_OF_INPUT, "<operations.flow.ret>");

    char* oprnd = crntFrm.popOperand();
    if (frames.size() > 1) frames[frames.size() - 2]->pushOperand(oprnd);
    else delete[] oprnd;
}

